/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// A source object for AAC audio files in ADTS format
// Implementation

#include "include/ADTSAudioFileSource.hh"
#include "include/InputFile.hh"
#include "../groupsock/include/GroupsockHelper.hh"

////////// ADTSAudioFileSource //////////

static unsigned const samplingFrequencyTable[16] = {
        96000, 88200, 64000, 48000,
        44100, 32000, 24000, 22050,
        16000, 12000, 11025, 8000,
        7350, 0, 0, 0
};

ADTSAudioFileSource *
ADTSAudioFileSource::createNew(UsageEnvironment &env, char const *fileName) {
    FILE *fid = NULL;
    do {
        fid = OpenInputFile(env, fileName);
        if (fid == NULL) break;

        // Now, having opened the input file, read the fixed header of the first frame,
        // to get the audio stream's parameters:
        unsigned char fixedHeader[4]; // it's actually 3.5 bytes long
        if (fread(fixedHeader, 1, sizeof fixedHeader, fid) < sizeof fixedHeader) break;

        // Check the 'syncword':
        if (!(fixedHeader[0] == 0xFF && (fixedHeader[1] & 0xF0) == 0xF0)) {
            env.setResultMsg("Bad 'syncword' at start of ADTS file");
            break;
        }

        // Get and check the 'profile':
        u_int8_t profile = (fixedHeader[2] & 0xC0) >> 6; // 2 bits
        if (profile == 3) {
            env.setResultMsg("Bad (reserved) 'profile': 3 in first frame of ADTS file");
            break;
        }

        // Get and check the 'sampling_frequency_index':
        u_int8_t sampling_frequency_index = (fixedHeader[2] & 0x3C) >> 2; // 4 bits
        if (samplingFrequencyTable[sampling_frequency_index] == 0) {
            env.setResultMsg("Bad 'sampling_frequency_index' in first frame of ADTS file");
            break;
        }

        // Get and check the 'channel_configuration':
        u_int8_t channel_configuration
                = ((fixedHeader[2] & 0x01) << 2) | ((fixedHeader[3] & 0xC0) >> 6); // 3 bits

        // If we get here, the frame header was OK.
        // Reset the fid to the beginning of the file:
#ifndef _WIN32_WCE
        rewind(fid);
#else
        SeekFile64(fid, SEEK_SET,0);
#endif
#ifdef DEBUG
        fprintf(stderr, "Read first frame: profile %d, "
            "sampling_frequency_index %d => samplingFrequency %d, "
            "channel_configuration %d\n",
            profile,
            sampling_frequency_index, samplingFrequencyTable[sampling_frequency_index],
            channel_configuration);
#endif
        return new ADTSAudioFileSource(env, fid, profile,
                                       sampling_frequency_index, channel_configuration);
    } while (0);

    // An error occurred:
    CloseInputFile(fid);
    return NULL;
}

ADTSAudioFileSource
::ADTSAudioFileSource(UsageEnvironment &env, FILE *fid, u_int8_t profile,
                      u_int8_t samplingFrequencyIndex, u_int8_t channelConfiguration)
        : FramedFileSource(env, fid) {
    fSamplingFrequency = samplingFrequencyTable[samplingFrequencyIndex];
    fNumChannels = channelConfiguration == 0 ? 2 : channelConfiguration;
    fuSecsPerFrame
            = (1024/*samples-per-frame*/* 1000000) / fSamplingFrequency/*samples-per-second*/;

    // Construct the 'AudioSpecificConfig', and from it, the corresponding ASCII string:
    unsigned char audioSpecificConfig[2];
    u_int8_t const audioObjectType = profile + 1;
    audioSpecificConfig[0] = (audioObjectType << 3) | (samplingFrequencyIndex >> 1);
    audioSpecificConfig[1] = (samplingFrequencyIndex << 7) | (channelConfiguration << 3);
    sprintf(fConfigStr, "%02X%02x", audioSpecificConfig[0], audioSpecificConfig[1]);
}

ADTSAudioFileSource::~ADTSAudioFileSource() {
    CloseInputFile(fFid);
}

// Note: We should change the following to use asynchronous file reading, #####
// as we now do with ByteStreamFileSource. #####
void ADTSAudioFileSource::doGetNextFrame() {
    // Begin by reading the 7-byte fixed_variable headers:
    unsigned char headers[7];
    if (fread(headers, 1, sizeof headers, fFid) < sizeof headers
        || feof(fFid) || ferror(fFid)) {
        // The input source has ended:
        handleClosure();
        return;
    }

    // Extract important fields from the headers:
    Boolean protection_absent = headers[1] & 0x01;
    u_int16_t frame_length
            = ((headers[3] & 0x03) << 11) | (headers[4] << 3) | ((headers[5] & 0xE0) >> 5);
#ifdef DEBUG
    u_int16_t syncword = (headers[0]<<4) | (headers[1]>>4);
    fprintf(stderr, "Read frame: syncword 0x%x, protection_absent %d, frame_length %d\n", syncword, protection_absent, frame_length);
    if (syncword != 0xFFF) fprintf(stderr, "WARNING: Bad syncword!\n");
#endif
    unsigned numBytesToRead
            = frame_length > sizeof headers ? frame_length - sizeof headers : 0;

    // If there's a 'crc_check' field, skip it:
    if (!protection_absent) {
        SeekFile64(fFid, 2, SEEK_CUR);
        numBytesToRead = numBytesToRead > 2 ? numBytesToRead - 2 : 0;
    }

    // Next, read the raw frame data into the buffer provided:
    if (numBytesToRead > fMaxSize) {
        fNumTruncatedBytes = numBytesToRead - fMaxSize;
        numBytesToRead = fMaxSize;
    }
    int numBytesRead = fread(fTo, 1, numBytesToRead, fFid);
    if (numBytesRead < 0) numBytesRead = 0;
    fFrameSize = numBytesRead;
    fNumTruncatedBytes += numBytesToRead - numBytesRead;

    // Set the 'presentation time':
    if (fPresentationTime.tv_sec == 0 && fPresentationTime.tv_usec == 0) {
        // This is the first frame, so use the current time:
        gettimeofday(&fPresentationTime, NULL);
    } else {
        // Increment by the play time of the previous frame:
        unsigned uSeconds = fPresentationTime.tv_usec + fuSecsPerFrame;
        fPresentationTime.tv_sec += uSeconds / 1000000;
        fPresentationTime.tv_usec = uSeconds % 1000000;
    }

    fDurationInMicroseconds = fuSecsPerFrame;

    // Switch to another task, and inform the reader that he has data:
    nextTask() = envir().taskScheduler().scheduleDelayedTask(0,
                                                             (TaskFunc *) FramedSource::afterGetting,
                                                             this);
}
